//
//  ECNotificationCenter.m
//  ECDevelopKit
//
//  Created by LittoCats on 8/14/14.
//  Copyright (c) 2014 Littocats. All rights reserved.
//

#import "ECSNotificationCenter.h"

#import "NSObject+ECS.h"

#import <objc/message.h>

typedef NS_ENUM(char, ECSNotificationType) {
    ECSNotificationTypeSelector,
    ECSNotificationTypeBlock
};

//****************************@interface ECNotification ()***************************************//

@interface ECSNotification () <NSCopying>

@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy) NSString *object;

@property (nonatomic, weak) id observer;


@property (nonatomic, assign) char type;

@property (nonatomic, assign) SEL selector;

@property (nonatomic, copy) void (^block)(id observer, ECSNotification *noti);

@property (nonatomic, strong) NSDictionary *userInfo;

@end

//*******************************@interface ECNotificationCenter ()************************************//

@interface ECSNotificationCenter ()

// name->observer->object->notification 表，其中 observer/object 为弱引用，释放时将被置空,对应的 notification
//  将被释放
@property (nonatomic, strong) NSMapTable *observerTable;

//  observer->[name] 存储 observer 与 name 的对应关系，用于移除 observer
@property (nonatomic, strong) NSMapTable *nameTable;
@end

//*******************************@implementation ECNotification************************************//

@implementation ECSNotification

- (NSString *)name
{
    return _name;
}
- (id)object
{
    return [NSObject findObjectById:_object];
}
- (NSDictionary *)userInfo 
{
    return _userInfo;
}

#pragma mark-
- (BOOL)invoke
{
    if (!_observer) return NO;
    
    switch (_type) {
        case ECSNotificationTypeSelector:
            if ([_observer respondsToSelector:_selector])
                objc_msgSend(_observer, _selector, [self copy]);
            break;
        case ECSNotificationTypeBlock:
            if (_block) _block(_observer, [self copy]);
            break;
        default:
            return NO;
            break;
    }
    self.userInfo = nil;
    return YES;
}

- (id)copyWithZone:(NSZone *)zone
{
    ECSNotification *noti = [[ECSNotification allocWithZone:zone] init];
    noti.name = self.name;
    noti.object = self.object;
    noti.observer = self.observer;
    noti.selector = self.selector;
    noti.block = self.block;
    noti.type = self.type;
    noti.userInfo = self.userInfo;
    return noti;
}
@end

//********************************@implementation ECNotificationCenter***********************************//

@implementation ECSNotificationCenter

+ (instancetype)defaultCenter
{
    static ECSNotificationCenter *defaultCenter;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        defaultCenter = [[self alloc] init];
    });
    return defaultCenter;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.observerTable = [NSMapTable strongToStrongObjectsMapTable];
        self.nameTable = [NSMapTable weakToStrongObjectsMapTable];
    }
    return self;
}

- (void)pushNotification:(ECSNotification *)noti
{
    if (!noti.name || ![noti.name isKindOfClass:[NSString class]] ){
        @throw [[NSException alloc] initWithName:@"[ECNotification center] error :" reason:@"notification name must be NSString ." userInfo:nil];
        return;
    }
    //加入 observer->[name] 表中，用于 remove操作
    NSMutableArray *nameArray = [_nameTable objectForKey:noti.observer];
    if (!nameArray) {
        nameArray = [NSMutableArray new];
        [_nameTable setObject:nameArray forKey:noti.observer];
    }
    [nameArray addObject:noti.name];
    
    //加入 name->observer->object->notification 表，
    NSMapTable *observers = [_observerTable objectForKey:noti.name];
    if (!observers) {
        observers = [NSMapTable weakToStrongObjectsMapTable];
        [_observerTable setObject:observers forKey:noti.name];
    }
    
    NSMapTable *objects = [observers objectForKey:noti.observer];
    if (!objects) {
        objects = [NSMapTable weakToStrongObjectsMapTable];
        [objects setObject:objects forKey:noti.observer];
    }
    
    if (noti.observer && (noti.selector || noti.block)){
        id object = noti.object ? noti.object : [NSNull null];
        [objects setObject:object forKey:noti];
    }
}
#pragma mark-
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject
{
    ECSNotification *noti = [ECSNotification new];
    noti.observer = observer;
    noti.selector = aSelector;
    noti.name = aName;
    noti.object = anObject;
    noti.type = ECSNotificationTypeSelector;
    
    [self pushNotification:noti];
}

- (void)addObserver:(id)observer
               name:(NSString *)aName
             object:(id)anObject
              queue:(NSOperationQueue *)queue
         usingBlock:(void (^)(id, ECSNotification *))block
{
    ECSNotification *noti = [ECSNotification new];
    noti.observer = observer;
    noti.block = block;
    noti.name = aName;
    noti.object = anObject;
    noti.type = ECSNotificationTypeBlock;
    
    [self pushNotification:noti];
}

- (void)removeObserver:(id)observer
{
    [self removeObserver:observer name:nil object:nil];
}
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject
{
    //如果 name 为空,则移除所有 observer
    if (!aName) {
        NSArray *names = [_nameTable objectForKey:observer];
        [_nameTable removeObjectForKey:observer];
        
        for (NSString *name in names) {
            // object 为空，移除所有
            if (!anObject)
                [[_observerTable objectForKey:name] removeObjectForKey:observer];
            else
                [[[_observerTable objectForKey:name] objectForKey:observer] removeObjectForKey:anObject];
        }
    }else{
        // object 为空，移除所有
        if (!anObject)
            [[_observerTable objectForKey:aName] removeObjectForKey:observer];
        else
            [[[_observerTable objectForKey:aName] objectForKey:observer] removeObjectForKey:anObject];
        [[_nameTable objectForKey:observer] removeObject:aName];
    }

}

#pragma mark-
- (void)postNotification:(ECSNotification *)notification
{
    [notification invoke];
}
- (void)postNotificationName:(NSString *)aName object:(id)anObject
{
    [self postNotificationName:aName object:anObject];
}
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
{
    NSEnumerator *enumerator = [[_observerTable objectForKey:aName] objectEnumerator];
    NSMapTable *objects;
    while (objects = [enumerator nextObject]) {
        ECSNotification *noti = [objects objectForKey:anObject ? anObject : [NSNull null]];
        noti.userInfo = aUserInfo;
        [noti invoke];
    }
}

@end
